iT邦幫忙

2026 iThome 鐵人賽

DAY 6
0
AI Engineering

AI 的駕馭之道:一個 AI Code Reviewer 的養成、評測與邊界實錄系列 第 6

Day 6|第一份 Skill:盲讀、入口枚舉閘與九面向(上)

  • 分享至 

  • xImage
  •  

簡短回顧

昨天我們先讓確定性工具(靜態分析、供應鏈、SAST)產生可重播的第一層證據。

今天開始處理工具無法單獨回答的部分:程式語意、路徑可達性,以及團隊自己的取捨。

那就繼續吧~

繼續規劃

Phase 3:深度審查

在真正進入深度檢查的條列清單前,我會先讓一個未載入本 Skill 檢查清單的獨立 subagent 做第一輪審查,目的是降低九面向清單造成的定錨。因為我們沒辦法叫一個已經讀過 A~I 規則的模型「假裝沒讀過、自由地看」,那種自由是假的。

所以第一輪審查被結構性外包給一個未載入本 Skill 檢查清單的 subagent(ncr-fresh-eyes),並設定三條 context 隔離規則:prompt 裡不准有 category 提示、不准有 severity 分類法、不准有先前 findings 或 scanner digest。它回傳的是待複驗的線索,不是可以直接寫入報告的結論。派不出去時寧可跳過並揭露,也不准主 agent inline 模擬,因為主 agent 已經讀過後面的規則。

進入九面向審查前:盤點關鍵操作與所有入口

在進入九個審查類型之前,我會先做一次「關鍵操作與入口盤點」(Critical Operation Enumeration)。白話來說,就是先列出這次變更可能觸發哪些會改變重要狀態、寫入敏感資料,或造成不可逆後果的操作;再把所有能呼叫它的 API、背景工作、排程、CLI 等入口逐條找出來。只有每條路徑都通過應有的驗證、授權或業務不變條件,才算過關;找不齊時,也不能宣稱這次審查是安全的。之所以需要這道閘門,是因為「找到一個 safety net 就停手」是審查時常見的盲點。

safety net 描述的是一種 reviewer(人和 AI 都會犯)的注意力失敗模式:

你在審一個危險操作(例如「把問卷標成已送出」),心裡的問題是「這有沒有防護?」。

你順著 code 找,也確實找到了一處防護:比如某個呼叫路徑上有跑驗證,大腦就把這個擔憂標記為「已解決」,停止搜尋。

但正確的問題從來不是「存不存在一張安全網」,而是「每一條通往這個操作的路徑是不是都會經過安全網」。

更直接的比喻是:

檢查大樓防火作為,找到一支滅火器就宣布安全。大樓有三個出入口,其中兩個沒有。滅火器「存在」和「每條逃生路線都有覆蓋」很明顯是兩回事。

深度審查的九個面向 CheckList

  1. A 風格
  2. B 簡潔
  3. C 安全
  4. D API 慣例
  5. E 架構
  6. F 資料取用與資料庫
  7. G 測試
  8. H 非 Python 檔
  9. I 回溯分析

每一類都要給獨立的 ✅、❌ 或 —(不適用,而且要寫明為什麼不適用):不能含糊帶過,這就是報告裡九宮格總評的資料來源。

在進入清單之前,先給你一把讀它的尺。

底下 A 到 I 一共五十幾條,但它們的份量不一樣。大部分是通用判準,成熟工具與模型通常能提供一部分基線覆蓋;我仍把它們列出來,要求審查流程逐項確認,不能因為模型「應該知道」就當作已經檢查過。

這份完整清單裡,我特別標出一組換一家公司就不一定成立的規則。 它們不是通用知識,而是我從實作、審查與踩坑中歸納出來的設定值:我接受了哪些風險、原則衝突時誰優先,以及哪些情境在醫療環境要升級處理。模型再強,也不會憑空知道我在這些取捨上的立場。

這些規則前面我加了一個

沒有 ★ 的通用判準仍然要檢查,只是可以先借助 linter 與模型建立基線,再確認實際 coverage 與證據。真正需要你投入最多判斷的,是把你們家對應的規則找出來,那才是 Day 3 說的「心中的一把尺」。

本文的 Critical 是我在這份 Skill 裡定義的合併門檻:代表這個問題在我的審查政策下必須先處理,不等同 CVSS,也不是宣稱所有團隊都該給它相同的風險等級。

A 風格

涵蓋以下面向:

  1. 單一函式過長,20 行是「警訊」,是否需要拆成不同函式?
  2. type hint,沒有 hint 的是否對於程式碼理解造成阻礙?
  3. 路徑操作優先使用 pathlib;既有 os.path 若更清楚,或遷移沒有實際收益,不必為改而改
  4. 命名與實際行為一致性
  5. 布林命名優先讓呼叫處容易閱讀,通常使用正向命名;若領域概念本來就是 disabled、revoked 或 blocked,則保留真正的領域語意
  6. ★ 簽名改了沒同步 docstring/type hint 是 Critical
  7. 可讀性門檻(reviewer 讀好幾遍才懂 → 要求 why-comment)
  8. 註解只複述 code → 建議刪
B 簡潔

涵蓋以下面向:

  1. 重複程式碼
  2. 過度工程(YAGNI)
  3. Rule of Three(第三次出現才抽)
  4. 無用 import/函式/檔案/依賴(AI 產 code 的高頻副產物)
  5. 隱藏副作用(副作用不禁止,但必須從命名或簽名看得出來)

若有衝突時優先序:KISS > DRY > YAGNI

KISS、DRY、YAGNI 這三個都是 Coding 時的重要原則:

KISS (Keep It Simple, Stupid) 保持簡單:寫出好懂、不複雜的程式碼。減少負擔:讓其他人也能一眼看懂。

DRY (Don't Repeat Yourself) 不要重複:相同的邏輯只寫一次。方便維護:改一個地方就全部更新。

YAGNI (You Aren't Gonna Need It) 不要過度設計:現在不需要的功能就別做。拒絕想像:不要為「以後可能用到」先寫程式。

C 安全

★ 最高優先,但用風險管理思維評估,不是一律否決。

風險管理,最近也有上 ISO 27001 LA 課程,裡面整理了四個因應對策:

  1. 風險接受:決定不採取額外的控制措施,由組織自行承擔該風險
  2. 風險分擔:與第三方簽訂合約或協議,將風險的部分或全部潛在財務/營運影響分攤出去。
  3. 風險修改:透過實施控制措施(Controls),來降低風險發生的機率或減少發生後的衝擊程度。這是實務中最普遍採用的處置方式。
  4. 風險避免:完全停止或取消引發該風險的業務活動、技術方案或作業流程,從根源消除風險來源。

我在 code review 時使用的參考指標,包含資料敏感度網路環境既有防禦,再綜合實際情境判斷適合採取 AcceptTransferAvoidMitigate

以下這些是一定要處理的:

  1. 輸入未驗證
  2. 未參數化的 SQL
  3. 寫死的固定憑證
  4. evalos.system:在這份 Skill 裡一律列為 Critical;這是我刻意採取的保守合併政策,不是業界通則

另有以下規則:

  1. 前端驗證不豁免後端
  2. ★ Critical 安全發現必附三件套:安全、最小且可重播的 PoC(HTTP 問題可用 curl;其他情境可用靜態證據、測試或最小重現腳本)+ blast radius(醫療觸發時必須明講 PHI 代價)+具體修法。PoC 只能在受控環境使用消毒資料驗證,切勿對真實資料或正式環境執行破壞性操作。

以下有豁免情境:

  1. ★ 內部套件庫的安裝憑證。同仁用 uv / poetry / pip 裝我們自己的套件時要帶憑證,而我們發的是一把權限最小的唯讀 token,只能讀取套件。

任何 LLM 看到「憑證出現在專案設定裡」都可能直接喊 Critical!但在這個情境裡,這把 token 唯讀、只能拉取內部套件,而且只能在內網使用。它的權限邊界是下載套件;這是我評估後接受的風險,不是沒有看見憑證存在。

D API 慣例

涵蓋:

  1. ★ URL 以 dash - 分隔,勿用 underscore _。尤其後端 API URL 應全小寫 → Critical(團隊硬慣例)
  2. ★ 新增或修改 API 時缺少後端驗證 Schema → Critical(這是我的合併門檻)
  3. HTTP 動詞與 safe/冪等語意不符 → Critical(GET 有副作用之類,RFC 9110)
  4. Authentication(身分驗證)≠ Authorization(授權):@jwt_required 只證明你是誰,不證明你能碰這筆資源
  5. ★ PII(Personally Identifiable Information,個人識別資訊)/PHI(Protected Health Information,受保護健康資訊)不進 URL:path 或 query 皆是如此,因為 URL 會落 access log、proxy log、直接開啟甚至會進瀏覽器歷史紀錄
  6. ★ 回應時間經驗法則:小於 150ms 是初始理想值,不是通用 SLO;仍要按呼叫頻率、資料量與執行環境調整容忍度
  7. ★ 批次處理必須明確定義原子性。預設採 all-or-nothing;若業務刻意允許 partial success,就要一併定義回應契約、重試與恢復方式。違反醫療資料的業務不變條件時升為 Critical
E 架構
  1. 瑣碎驗證歸 Schema 層:例如基本資料型態、長度驗證等
  2. 業務邏輯不進 Repository:Repository 只做 DB 互動:查詢、寫入,業務規則就該搬去適合的模組中
  3. API Endpoint 只留薄編排:Schema 驗證 → 呼叫 repo → 組回應 → 簡單分派。當真正的業務規則、多步驟流程、可重用邏輯開始在 endpoint 裡長出來,這個 endpoint 就已經悄悄變成了 service 層,就該搬走了!
  4. ★ 一個設計決策被硬編碼進 N 個模組,改一處要同步改 N 處:後果不可逆時升 Critical

三條規則的出生證明

上面那張清單,每一條的份量看起來都一樣。實際上不是:有些是踩過才補上的,有些是只有在我們這裡才成立的。挑三條講清楚它們為什麼在那裡,以及它們會冤枉誰。

A-6 簽名改了、docstring 沒跟上,為什麼判 Critical

同一份清單裡「註解只複述 code」我只建議刪掉,這條卻是 Critical。差別在成本落在誰身上。

審查到這裡的時候,我已經花力氣比對過簽名跟 docstring、確認它真的不一致了。如果這時候放過,那份力氣就只買到我自己知道。下一個人、下一個 agent 讀到這個函式,會照著那份過時的說明去理解、去呼叫,然後在同一個地方摔一跤,花一樣的時間、付一樣的成本,把同一個問題重新排除一次。

發現的成本已經付過了。不修,等於讓它被重複支付。

會誤傷的情況:純粹補型別、不動行為的 commit。那種 diff 裡簽名確實變了但語意沒變,這時候要看的是 docstring 描述的行為還成不成立,不是欄位有沒有逐字對上。

D-1 URL 一律用 dash,這條不是通則

這是我們團隊的硬慣例,不是業界共識。我把它寫進 skill 並判 Critical,是因為它是一個設定值,不是一項知識:模型再強也不會知道我們選了 dash 而不是 underscore。這種東西不寫下來,它就不存在。

會誤傷的情況:路徑不是我們定的時候。串接第三方 API、或者這個 endpoint 是沿用既有系統的舊風格,那就不適用;規約管的是我們自己新開的路徑。

D-7 批次處理的原子性,在這裡升為 Critical

醫師一次開立五種藥,這五筆資料代表的是同一個完整用藥決策,不是五筆互不相干的寫入。如果批次處理到第三筆時失敗,系統只留下前兩種,卻仍讓下游把它當成完整處方,病人接收到的就不是醫師原本開立的治療方案。

所以這裡判 Critical。原子性保護的不是資料筆數,而是醫療決策的完整性。如果業務刻意允許 partial success,就必須讓「尚未完成」成為明確狀態,並定義重試與恢復方式,不能讓部分結果冒充完整結果。

會誤傷的情況:不同資料本來就允許獨立成功,而且系統已明確定義 partial success、重試與恢復契約。這時不適用上述「完整用藥決策不可被拆開」的 Critical 判定。

本日小結

今天先擋掉兩個結構性的問題:用未載入檢查清單的獨立 subagent 降低定錨,再用關鍵操作與入口盤點,擋掉「找到一張安全網就收手」。然後才走進九面向清單的前五類。

你也可以開始盤點自己 in-house 的規則:哪些是通用檢查,哪些是只有你們團隊才知道的取捨?把後者的理由、適用範圍與例外一起寫進 Skill,這份清單才會真的變成你們自己的審查標準。

明天把九面向的後四類走完(F 資料取用與資料庫、G 測試、H 非 Python 檔、I 回溯分析),再補上 CodeGraph 這個結構視角、subagent 的命名規約,以及進報告前的最後一道閘。


上一篇
Day 5|第一份 Skill:確定性工具的軌道
下一篇
Day 7|第一份 Skill:AI Code Review 九面向(下)與兩道閘
系列文
AI 的駕馭之道:一個 AI Code Reviewer 的養成、評測與邊界實錄7
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言